#include <ximera/system/application.hpp>
#include <ximera/logger.hpp>

#include <iostream>
#include <fstream>
using namespace ximera;

struct ChiminikeDemoHandler : system::AppHandler
{
    ChiminikeDemoHandler(system::Application& app)
        : application(app) {}

    virtual void onStart(system::AppEventArgs& event)
    {
        try
        {
            setupInput();
            setupWorld();
            setupCamera();
            setupLights();
            setupEntities();
            setupParticles();
            setupAudio();
        }
        catch (ximera::Exception& e)
        {
            application.shouldClose(true);
            XIMERA_LOG() << e << std::endl;
        }
    }

    virtual void onUpdate(system::UpdateEventArgs& event)
    {
        jaiqua->step(event.secs);

        math::Vector3 moveDir;

        if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::W))
            moveDir = camera->direction();
        else if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::S))
            moveDir = -camera->direction();

        if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::A))
            moveDir += -camera->right();
        else if (event.app.inputSystem()->keyboard()->isKeyPressed(input::KeyCode::D))
            moveDir += camera->right();

        if (moveDir != math::Vector3::ZERO)
        {
            Real const velocity = 20.0f;
            camera->position(camera->position() + (moveDir * (velocity * event.secs)));
        }

        Real rotation = -event.app.inputSystem()->mouse()->axisRel(1);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 40.05f;
            rotation = rotation * velocity;

            camera->pitch(rotation);
        }

        rotation = -event.app.inputSystem()->mouse()->axisRel(0);

        if (!math::util::equal(rotation, 0))
        {
            Real const velocity = 35.0f;
            rotation = rotation * velocity;

            camera->yaw(rotation);
        }

        listener->position(camera->position());
    }

private:
    system::Application& application;

    graphics::IWorldPtr graphicsWorld;
    graphics::ICameraPtr camera;
    graphics::IViewportPtr viewport;
    
    graphics::ILightPtr light;
    graphics::ILightPtr light2;
    graphics::ILightPtr sunLight;
    
    graphics::IEntityPtr jaiqua;
    graphics::IEntityPtr robot;
    
    graphics::IParticleSystemPtr smoke;
    graphics::IParticleSystemPtr rain;
    graphics::IParticleSystemPtr flare;
    graphics::IParticleSystemPtr sun;

    audio::IWorldPtr audioWorld;
    audio::IListenerPtr listener;
    audio::ISoundPtr sound;

    void setupInput()
    {
        application.inputSystem()->keyboard()->onKeyDown() += 
            boost::bind(&ChiminikeDemoHandler::keyboard_KeyDown, this, _1, _2);
    }

    void setupWorld()
    {
        graphicsWorld.reset(application.graphicsSystem()->createWorld());
        
        application.graphicsSystem()->ambientLight(math::Color(0.5f, 0.5f, 0.5f));
        application.graphicsSystem()->skyDome(L"Examples/CloudySky");
    }

    void setupCamera()
    {
        camera.reset(graphicsWorld->createCamera(L"default"));
        camera->position(math::Vector3(70, 20, -50));
        camera->lookAt(math::Vector3(0, 0, 0));

        viewport.reset(graphicsWorld->createViewport(camera.get()));
    }

    void setupLights()
    {
        light.reset(graphicsWorld->createLight(L"light"));
        light->position(math::Vector3(20,80,50));
        light->diffuseColor(math::Color(10, 110, 0, 255));
        light->type(graphics::LightType::Directional);

        light2.reset(graphicsWorld->createLight(L"light2"));
        light2->position(math::Vector3(-20,80,50));
        light2->enabled(false);
        light2->type(graphics::LightType::Point);

        sunLight.reset(graphicsWorld->createLight(L"sunLight"));
        sunLight->type(graphics::LightType::Directional);
    }

    void setupEntities()
    {
        robot.reset(graphicsWorld->createEntity(L"robot", L"robot.mesh"));
        graphics::ISceneNode* entityNode = graphicsWorld->rootNode()->createChild(L"robot");
        entityNode->attach(robot.get());
        entityNode->position(math::Vector3(0, 0, 0));
        entityNode->scale(.5f);

        jaiqua.reset(graphicsWorld->createEntity(L"cube", L"jaiqua.mesh"));
        jaiqua->enableAnimation(L"Sneak");
        entityNode = graphicsWorld->rootNode()->createChild(L"jaiqua");
        entityNode->attach(jaiqua.get());
        entityNode->position(math::Vector3(-20, 0, 0));        
    }

    void setupParticles()
    {
        smoke.reset(graphicsWorld->createParticleSystem(L"smoke", L"Examples/Smoke"));
        smoke->speedFactor(0.3f);
        graphics::ISceneNode* particleNode = graphicsWorld->rootNode()->createChild(L"smoke");
        particleNode->attach(smoke.get());
        particleNode->position(math::Vector3(15, -60, 50));

        rain.reset(graphicsWorld->createParticleSystem(L"rain", L"Examples/Rain"));
        particleNode = graphicsWorld->rootNode()->createChild(L"rain");
        particleNode->attach(rain.get());
        particleNode->position(math::Vector3(0, 80, 0));

        flare.reset(graphicsWorld->createParticleSystem(L"flare", L"Examples/GreenyNimbus"));
        particleNode = graphicsWorld->rootNode()->createChild(L"flare");
        particleNode->attach(flare.get());
        particleNode->position(math::Vector3(0, 0, 0));

        sun.reset(graphicsWorld->createParticleSystem(L"sun", L"Space/Sun"));
        particleNode = graphicsWorld->rootNode()->createChild(L"sun");
        particleNode->attach(sun.get());
        particleNode->position(math::Vector3(-30, 150, 0));
    }

    void setupAudio()
    {
        audioWorld.reset(application.audioSystem()->createWorld());
        listener.reset(audioWorld->createListener());
        
        sound.reset(audioWorld->loadSound(L"media/sounds/explosion.wav"));
        sound->loopMode(audio::LoopMode::On);
        sound->play();
    }

    void keyboard_KeyDown(input::IKeyboard* sender, input::KeyCode const& keyCode)
    {
        switch (keyCode.value)
        {
        case input::KeyCode::Escape:
            application.shouldClose(true);
            break;

        case input::KeyCode::L:
            light->enabled(!light->enabled());
            light2->enabled(!light2->enabled());
            break;

        case input::KeyCode::_1:
            if (camera->renderMode() == graphics::RenderMode::Solid)
                camera->renderMode(graphics::RenderMode::Wireframe);
            else
                camera->renderMode(graphics::RenderMode::Solid);
            break;

        case input::KeyCode::_2:
            smoke->visible(!smoke->visible());
            break;

        case input::KeyCode::_3:
            rain->visible(!rain->visible());
            break;

        case input::KeyCode::_4:
            flare->visible(!flare->visible());
            break;

        case input::KeyCode::_5:
            if (application.graphicsSystem()->isSkyDomeEnabled())
                application.graphicsSystem()->disableSkyDome();
            else
                application.graphicsSystem()->skyDome(L"Examples/CloudySky");
        }
    }
};

int main()
{
    try
    {
        XIMERA_LOG().stream(&std::wcout);

        system::AppSettings settings;
        settings[L"ximera.plugins.input"] = XIMERA_PLUGIN(L"XimeraDInput8");
        settings[L"ximera.plugins.graphics"] = XIMERA_PLUGIN(L"XimeraOgre");
        settings[L"ximera.plugins.audio"] = XIMERA_PLUGIN(L"XimeraOpenAL");
        settings[L"ximera.ogre.showdialog"] = L"1";

        system::Application app(settings);

        ChiminikeDemoHandler handler(app);
        app.addHandler(handler);

        return app.run();
    }
    catch (ximera::Exception& e)
    {
        XIMERA_LOG() << e << std::endl;
    }
}
